﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
using System.Threading.Tasks;
using System.IO;
using System.Collections.Specialized;
using System.Data.Services.Client;
using System.Text;
using com.dynamicdeploy.storage.blobcopy;

namespace DDMigrateStorageAccountsAsync
{
    public class StorageAccountsClass
    {
        public string SourceStorageAccount { get; set; }

        public string DestinationStorageAccount { get; set; }

        public string JobId { get; set; }

        private long _tid = 0;

        public long Tid
        {
            get { return _tid; }
            set { _tid = value; }
        }
       
    }

    class Program
    {
        public const string CUSTOM_DATE_FORMAT_SHORT = "dMMMyyyy";
        #region Sync constants
        private const string LOGS_CONTAINER_NAME_CONFIG = "com.dynamicdeploy.ddmigratestorageaccountsasync.LogsContainer";
        private static string LOGS_CONTAINER_NAME = "ddmigratestorageaccountasynclogs";
        private static string MIGRATION_LOGS_CONTAINER_NAME = "ddoldaccountmigrationasynclogs";
        #endregion
        #region constants for scheduler


        internal const int TIMEINSECSFORINFINITEINITWAIT = 60;
        internal const int BACKUP_INITIAL_WAIT_MILLIS = 300000;

        public static string DEPLOYMENT_ID = null;
        public static string ROLE_ID = null;
        public static string INSTANCE_ID = null;

        const string LOG_FILE_NAME = "DDMigrateStorageAccountsAsync.log";

        public static string AppRoot = "";
        public static string RoleRoot = "";
        public static string SitesRoot = "";
        public static readonly RetryPolicy RETRY_POLICY = RetryPolicies.Retry(3, TimeSpan.Zero);
        #endregion
        private static bool ENABLE_SERVICE = false;


        private static QueueListener BlobCopyQueueListenerAsync { get; set; }
        private static QueueListener BlobCopyQueueListenerSync { get; set; }
       
        private static int maxParallelJobs = 1;
        private static int jobCounter = 0;
        private static WAStorageHelper StorageHelper
        {
            get;
            set;
        }


        private static int DEQUEUE_COUNT = 1;

        private static string DB_CONNECTION_STRING = "";

        public static TableStorageHelper TableHelper;


        private static void InitTraceOptions()
        {
            DefaultTraceListener drl;
            drl = (DefaultTraceListener)Trace.Listeners["Default"];
            drl.LogFileName = LOG_FILE_NAME;

            drl.TraceOutputOptions = TraceOptions.DateTime;
            Trace.TraceInformation("=====================================================================================================================================");

            Trace.TraceInformation(string.Format("{0}\t{1} in working directory {2}", DateTime.Now.ToString(), "Starting DDMigrateStorageAccountsAsync plugin ", Environment.CurrentDirectory));

        }

        private static void InitConfigPublisher()
        {
            Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Start SetupConfigurationSettingPublisher()"));
            SetupConfigurationSettingPublisher();
            Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "End SetupConfigurationSettingPublisher()"));
            ENABLE_SERVICE = bool.Parse(GetConfigValueAsString("com.dynamicdeploy.ddmigratestorageaccountsasync.EnableService"));

        }

        private static void SetupConfigurationSettingPublisher()
        {
            // This code sets up a handler to update CloudStorageAccount instances when their corresponding
            // configuration settings change in the service configuration file.
            Trace.TraceInformation("Setting up configuration setting publishing");
            CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
            {
                // Provide the configSetter with the initial value
                configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));

                RoleEnvironment.Changed += (sender, arg) =>
                {
                    if (arg.Changes.OfType<RoleEnvironmentConfigurationSettingChange>()
                        .Any((change) => (change.ConfigurationSettingName == configName)))
                    {
                        // The corresponding configuration setting has changed, propagate the value
                        if (!configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)))
                        {
                            // In this case, the change to the storage account credentials in the
                            // service configuration is significant enough that the role needs to be
                            // recycled in order to use the latest settings. (for example, the 
                            // endpoint has changed)
                            RoleEnvironment.RequestRecycle();
                        }
                    }
                };
            });
        }

        #region Utility Functions
        public static string GetConfigValueAsString(string configName)
        {
            return (RoleEnvironment.IsAvailable)
                                   ? RoleEnvironment.GetConfigurationSettingValue(configName)
                                   : System.Configuration.ConfigurationManager.AppSettings[configName];

        }

        public static string ExpandKeywords(string buffer)
        {
            if (RoleEnvironment.IsAvailable)
            {
                buffer = buffer.Replace("$approot$", AppRoot);
                buffer = buffer.Replace("$deploymentid$", RoleEnvironment.DeploymentId);
                buffer = buffer.Replace("$roleinstanceid$", RoleEnvironment.CurrentRoleInstance.Id);
                buffer = buffer.Replace("$computername$", Environment.MachineName);
                buffer = buffer.Replace("$guid$", Guid.NewGuid().ToString());
                buffer = buffer.Replace("$now$", DateTime.Now.ToString());
                buffer = buffer.Replace("$roleroot$", Environment.GetEnvironmentVariable("RoleRoot"));

                if (!string.IsNullOrEmpty(SitesRoot))
                {

                    buffer = buffer.Replace("$sitesroot$", SitesRoot);
                }
            }
            return buffer;
        }
        public static void FillEnvironmentVariables()
        {
            if (RoleEnvironment.IsAvailable)
            {
                try
                {
                    RoleRoot = Environment.GetEnvironmentVariable("RoleRoot");

                    AppRoot = Environment.GetEnvironmentVariable("RoleRoot") + @"\approot";
                    SitesRoot = Environment.GetEnvironmentVariable("RoleRoot") + @"\sitesroot";
                    if (!Directory.Exists(SitesRoot))
                    {
                        SitesRoot = null;
                        // May be WorkerRole
                        //AppRoot = Environment.GetEnvironmentVariable("RoleRoot") + @"\approot";
                        //SitesRoot = AppRoot;
                        if (RoleEnvironment.IsEmulated)
                        {

                            SitesRoot = AppRoot;
                        }
                    }

                    Trace.TraceInformation("Sites Root " + ((!string.IsNullOrEmpty(SitesRoot)) ? SitesRoot : "Seems to be a Worker Role"));
                    Trace.TraceInformation("AppRoot " + AppRoot);


                }
                catch (Exception ex)
                {
                    Trace.TraceError("Error retrieving environment variables and config variables in FillEnvironmentVariables() " + ex.Message);

                }
            }
        }

        #endregion

        #region Logs
        static void UploadLogFile()
        {

            try
            {

                string filePath = Path.Combine(Environment.CurrentDirectory, LOG_FILE_NAME);
                if (File.Exists(filePath) && new FileInfo(filePath).Length > 0)
                {
                    string logsContainer = LOGS_CONTAINER_NAME;
                    if (!string.IsNullOrEmpty(GetConfigValueAsString("com.dynamicdeploy.ddmigratestorageaccountsasync.LogsContainer")))
                    {
                        logsContainer = GetConfigValueAsString("com.dynamicdeploy.ddmigratestorageaccountsasync.LogsContainer");
                    }
                    var logs = CloudStorageAccount.Parse(GetConfigValueAsString("com.dynamicdeploy.ddmigratestorageaccountsasync.StorageAccountConnectionString")).CreateCloudBlobClient().GetContainerReference(logsContainer);
                    logs.CreateIfNotExist();
                    string customDT = DateTime.UtcNow.ToString(CUSTOM_DATE_FORMAT_SHORT).Replace(" ", "");
                    var error = logs.GetBlobReference(string.Format("{0}/{1}/{2}/{3}{4}.log", DEPLOYMENT_ID, RoleEnvironment.CurrentRoleInstance.Id, customDT, "ddmigratestorageaccountasync-", (DateTime.MaxValue - DateTime.UtcNow).Ticks.ToString("d19")));
                    error.Properties.ContentType = "text/plain";


                    Trace.TraceInformation("=====================================================================================================================================");

                    error.UploadFile(filePath);
                    File.WriteAllText(filePath, string.Empty);
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError("Error uploading log file " + ex.Message);
            }
        }
        #endregion
        static void Main(string[] args)
        {
            try
            {
                InitTraceOptions();
                InitConfigPublisher();
                if (ENABLE_SERVICE)
                {
                    Trace.TraceInformation("Service enabled.");
                    // Register event handler for roleinstance stopping  and changed events
                    if (RoleEnvironment.IsAvailable)
                    {
                        RoleEnvironment.Stopping += new EventHandler<RoleEnvironmentStoppingEventArgs>(RoleEnvironment_Stopping);

                        DEPLOYMENT_ID = RoleEnvironment.DeploymentId;
                        INSTANCE_ID = RoleEnvironment.CurrentRoleInstance.Id;
                        ROLE_ID = RoleEnvironment.CurrentRoleInstance.Role.Name;
                    }
                    else
                    {
                        DEPLOYMENT_ID = System.Guid.NewGuid().ToString("N");
                        INSTANCE_ID = "1";
                        ROLE_ID = Environment.MachineName;
                    }



                    FillEnvironmentVariables();


                    try
                    {




                        while (!InitializeAndStart())
                        {
                            Trace.TraceError("There was an error starting the service. Will go into infinite loop trying for seconds " + TIMEINSECSFORINFINITEINITWAIT);
                            Thread.Sleep(TimeSpan.FromSeconds(TIMEINSECSFORINFINITEINITWAIT));

                        }



                    }
                    catch (Exception ex)
                    {

                        Trace.TraceError("Error configuring SERVICE. Quitting..." + ex.Message);

                        // Environment.Exit(-1);
                    }

                    //Keep on checking the drives in a loop
                    while (true)
                    {

                        try
                        {

                            try
                            {
                                Thread.Sleep(TimeSpan.FromSeconds(Program.TIMEINSECSFORINFINITEINITWAIT * 10));
                                UploadLogFile();

                            }
                            catch (Exception ex)
                            {
                                Trace.TraceError("Fail in Run " + ex.Message);
                            }


                        }
                        catch (Exception ex)
                        {

                            Trace.TraceInformation("Error uploading log file " + ex.Message);
                        }





                    }

                }
                else
                {

                    Trace.TraceInformation("Service not enabled.");

                }


                RoleEnvironment.Changed += new EventHandler<RoleEnvironmentChangedEventArgs>(RoleEnvironment_Changed);

            }
            catch (Exception ex)
            {

                Trace.TraceError("Error " + ex.Message);
            }
        }


        #region Service Configuration


        static bool InitializeAndStart()
        {
            Trace.WriteLine("Starting Service");
            bool allIsWell = true;


            try
            {
                allIsWell = IniServiceProperties();

                if (allIsWell)
                {
                    CheckConnection();
                    Trace.TraceInformation("Initialized Storage Account.");

                    CreateTables();
                    Trace.TraceInformation("Initialized Tables.");

                    //Init queues
                    InitQueues();
                    Trace.TraceInformation("Initialized Queues.");

                }//alliswell

            }
            catch (Exception ex)
            {
                Trace.TraceError("Error in ConfigureArrOnce() " + ex.Message);
                allIsWell = false;
            }

            return allIsWell;
        }


        private static bool IniServiceProperties()
        {



            int vt = WindowsAzureSystemHelper.GetIntConfigurationValue("com.dynamicdeploy.ddmigratestorageaccountsasync.queuemessagevisibilitytimeoutmins");
            if (vt == 0)
            {
                QueueListener.VisibilityTimeout = TimeSpan.Zero;
            }
            else
            {

                QueueListener.VisibilityTimeout = TimeSpan.FromMinutes(vt);
            }

            DEQUEUE_COUNT = WindowsAzureSystemHelper.GetIntConfigurationValue("com.dynamicdeploy.ddmigratestorageaccountsasync.dequeuecount");

            maxParallelJobs = WindowsAzureSystemHelper.GetIntConfigurationValue("com.dynamicdeploy.ddmigratestorageaccountsasync.maxparalleljobs");

            DB_CONNECTION_STRING = WindowsAzureSystemHelper.GetConfigValueAsString("com.dynamicdeploy.ddmigratestorageaccountsasync.cpfdbConnectionString");

            return true;

        }

        #region Initilize Storage Account

        private static void LoadStorageAccount()
        {
            try
            {

                StorageHelper = new WAStorageHelper("com.dynamicdeploy.ddmigratestorageaccountsasync.StorageAccountConnectionString");



            }
            catch (Exception ex)
            {

                Trace.TraceError("LoadStorageAccount-->" + ex.Message);
            }

        }

        private static void CheckConnection()
        {
            if (StorageHelper == null)
                LoadStorageAccount();

        }
        private static void CreateTables()
        {
            StorageHelper.CreateTableIfNotExist(BlobCopyStatus.TABLE_NAME);
            TableHelper = new TableStorageHelper("com.dynamicdeploy.ddmigratestorageaccountsasync.StorageAccountConnectionString", RoleEnvironment.IsAvailable);

        }
        #endregion


        #region Queue Functions
        /// <summary>
        /// Init queues. Primarily, it used the key modelcompletionqueuename to track the completion status of model runners.
        /// </summary>
        private static void InitQueues()
        {
            BlobCopyQueueListenerAsync = StorageHelper.GetQueueListener(WindowsAzureSystemHelper.GetStringConfigurationValue("com.dynamicdeploy.ddmigratestorageaccountsasync.migratestorageasyncqueuename"));
            //Create the event handler to receive and event when the message arrives in the queue.
            BlobCopyQueueListenerAsync.MessageReceived += new MessageReceivedEventHandler(BlobCopyQueueListenerAsync_MessageReceived);

            if (BlobCopyQueueListenerAsync.StartReceiving())
            {
                Trace.TraceInformation(String.Format("Automatic Message receiving started for {0}...", BlobCopyQueueListenerAsync.Queue.Name));

            }


            BlobCopyQueueListenerSync = StorageHelper.GetQueueListener(WindowsAzureSystemHelper.GetStringConfigurationValue("com.dynamicdeploy.ddmigratestorageaccountsasync.migratestoragequeuename"));
            //Create the event handler to receive and event when the message arrives in the queue.
            BlobCopyQueueListenerSync.MessageReceived += new MessageReceivedEventHandler(BlobCopyQueueListenerSync_MessageReceived);

            if (BlobCopyQueueListenerSync.StartReceiving())
            {
                Trace.TraceInformation(String.Format("Automatic Message receiving started for {0}...", BlobCopyQueueListenerSync.Queue.Name));

            }

        }




        /// <summary>
        /// This even is fired every time a modelrunner completes a job and send a message to the migratestoragequeue
        /// [comma separated list of request ids]
        /// action: migratestorageaccount
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void BlobCopyQueueListenerAsync_MessageReceived(object sender, MessageReceivedEventArgs e)
        {
            Trace.TraceInformation("Received request ");
            try
            {
                if (e.Message != null || e.Message.DequeueCount <= DEQUEUE_COUNT)
                {
                    //Check the capacity of the finalizer
                    if (jobCounter < maxParallelJobs)
                    {

                        Trace.TraceInformation("Received request " + e.Message.AsString);
                        string[] arr = e.Message.AsString.Split(new char[] { ';', ',', ' ' });

                        if (arr != null && arr.Length > 0)
                        {


                            long[] requestIds = Array.ConvertAll(arr, element => long.Parse(element.Trim()));

                            Task.Factory.StartNew(() =>
                                {
                                    try
                                    {
                                        Trace.TraceInformation("Processing request " + e.Message.AsString);
                                        BlobCopyUtil.RunCopyBlob(DB_CONNECTION_STRING, requestIds);
                                        Trace.TraceInformation("Processed request " + e.Message.AsString);
                                        StorageHelper.DeleteMessage(BlobCopyQueueListenerAsync.Queue.Name, e.Message);
                                        Trace.TraceInformation("Deleted queue message " + e.Message.AsString);

                                    }
                                    catch (Exception ex)
                                    {

                                        Trace.TraceError(string.Format("Error executing tasks for request {0}. Exception: {1}.", e.Message.AsString, ex.Message));
                                    }
                                });

                          

                        }//if
                    }//if
                    else
                    {
                        Trace.TraceError(String.Format("Exceeded parallel jobs allowed. Allowed {0}, Active {1}. Waiting...", maxParallelJobs, jobCounter));

                    }
                }
                else
                {
                    Trace.TraceError("Received null message or exceeded dequeue count for message.");

                }

            }
            catch (Exception ex)
            {

                Trace.TraceError("Exception in ProvisioningRequestQueue_MessageReceived-->" + ex.Message);

            }

        }

        /// <summary>
        /// This even is fired every time a modelrunner completes a job and send a message to the migratestoragequeue
        /// [action]:{sourcestorageaccountconnectionstring}{destinationstorageaccountconnectionstring}
        /// action: migratestorageaccount
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void BlobCopyQueueListenerSync_MessageReceived(object sender, MessageReceivedEventArgs e)
        {

            try
            {
               
                if (e.Message != null || e.Message.DequeueCount <= DEQUEUE_COUNT)
                {
                    //Check the capacity of the finalizer
                    if (jobCounter < maxParallelJobs)
                    {


                        string[] arr = e.Message.AsString.Split(new char[] { ':' }, 3);

                        if (arr != null && arr.Length > 1)
                        {

                            string command = arr[0];

                            if (command.IndexOf("migratestorageaccount") > -1)
                            {

                                string[] storageAccounts = arr[1].Split('$');

                                string sourceStorageAccount = MacrosProcessingService.GetMacrosListFromContent(storageAccounts[0], true)[0];
                                string destStorageAccount = MacrosProcessingService.GetMacrosListFromContent(storageAccounts[1], true)[0];
                                string jobId = MacrosProcessingService.GetMacrosListFromContent(storageAccounts[2], true)[0];

                                Trace.TraceInformation("JobId=." + jobId);

                                sourceStorageAccount = sourceStorageAccount.Replace("{", "").Replace("}", "");

                                destStorageAccount = destStorageAccount.Replace("{", "").Replace("}", "");

                                jobId = jobId.Replace("{", "").Replace("}", "");

                                StorageAccountsClass sac = new StorageAccountsClass()
                                {
                                    SourceStorageAccount = sourceStorageAccount,
                                    DestinationStorageAccount = destStorageAccount,
                                    JobId = jobId
                                };

                                if (storageAccounts.Length > 3)
                                {
                                    long tid = 0;
                                    try
                                    {
                                        tid = long.Parse(MacrosProcessingService.GetMacrosListFromContent(storageAccounts[3], true)[0]);


                                    }
                                    catch (Exception)
                                    {

                                        sac.Tid = tid;
                                    }

                                }

                                ThreadPool.QueueUserWorkItem(ProcessStorageMigrationRequest, sac);

                                Trace.TraceInformation("Queued job JobId=." + jobId);
                                StorageHelper.DeleteMessage(BlobCopyQueueListenerSync.Queue.Name, e.Message);

                                Trace.TraceInformation("Deleted queue message JobId=." + jobId);
                            }//process only migrate requests





                        }//if
                    }//if
                    else
                    {
                        Trace.TraceError(String.Format("Exceeded parallel jobs allowed. Allowed {0}, Active {1}. Waiting...", maxParallelJobs, jobCounter));

                    }
                }
                else
                {
                    Trace.TraceError("Received null message or exceeded dequeue count for message.");

                }

            }
            catch (Exception ex)
            {

                Trace.TraceError("Exception in ProvisioningRequestQueue_MessageReceived-->" + ex.Message);

            }

        }

        private static void ProcessStorageMigrationRequest(object state)
        {
            TableServiceContext context = TableHelper.Context;
            string jobId = "";
            try
            {
                StorageAccountsClass s = state as StorageAccountsClass;
                jobId = s.JobId;
                Trace.TraceInformation("Processing job JobId=" + s.JobId);

                string customDT = DateTime.UtcNow.ToString(CUSTOM_DATE_FORMAT_SHORT).Replace(" ", "");
                string blobName = string.Format("{0}/{1}/{2}/{3}/{4}{5}.log", s.Tid, s.JobId, RoleEnvironment.CurrentRoleInstance.Id, customDT, "starting-", (DateTime.MaxValue - DateTime.UtcNow).Ticks.ToString("d19"));
                context.AddObject(BlobCopyStatus.TABLE_NAME, new BlobCopyStatus()
                {
                    CopyStatus = "STARTING",
                    DestinationUri = s.DestinationStorageAccount,
                    JobId = s.JobId,
                    SourceUri = s.SourceStorageAccount,
                    Tid = s.Tid
                });

                try { context.SaveChanges(); }

                catch (Exception e)
                {
                    Trace.TraceError("Exception in inserting blob copy status messages -->" + e.Message);
                }
                finally
                {

                    StorageHelper.PutBlob(MIGRATION_LOGS_CONTAINER_NAME, blobName, "Starting Migration Operation");
                }
               


                var copyResults = BlobCopyUtil.CopyStorageAccount(s.JobId, s.SourceStorageAccount, s.DestinationStorageAccount);



                StringBuilder strb = new StringBuilder();
                strb.Append("JobId,Tid,SourceUri,DestinationUri,CopyStatus,StatusMessage").Append(Environment.NewLine);

                foreach (BlobCopyStatus bc in copyResults)
                {


                    strb.Append(bc.ToCSVString()).Append(Environment.NewLine);

                }

                context.AddObject(BlobCopyStatus.TABLE_NAME, new BlobCopyStatus()
                {
                    CopyStatus = "COMPLETE",
                    DestinationUri = s.DestinationStorageAccount,
                    JobId = s.JobId,
                    SourceUri = s.SourceStorageAccount,
                    Tid = s.Tid
                });
                try { context.SaveChanges(); }
                catch (Exception e)
                {
                    Trace.TraceError("Exception in inserting blob copy status messages -->" + e.Message);
                }
                finally
                {
                    customDT = DateTime.UtcNow.ToString(CUSTOM_DATE_FORMAT_SHORT).Replace(" ", "");
                    blobName = string.Format("{0}/{1}/{2}/{3}/{4}{5}.csv", s.Tid, s.JobId, RoleEnvironment.CurrentRoleInstance.Id, customDT, s.JobId + "-", (DateTime.MaxValue - DateTime.UtcNow).Ticks.ToString("d19"));
                    StorageHelper.PutBlob(MIGRATION_LOGS_CONTAINER_NAME, blobName, strb.ToString());
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError("Exception in copying storage accounts " + ex.Message);

            }
            finally
            {

                Trace.TraceInformation("Processed job JobId=" + jobId);
            }

        }
      
        #endregion
        static void RoleEnvironment_Changed(object sender, RoleEnvironmentChangedEventArgs e)
        {
            try
            {
                Trace.WriteLine(string.Format("RoleEnvironment_Changed {0}", e.Changes.Count));
                if (e.Changes.Any(chg => chg is RoleEnvironmentTopologyChange))
                {
                    Trace.WriteLine("RoleEnvironmentTopologyChange");
                    InitializeAndStart();
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(string.Format("Exception on RoleEnvironment_Changed: '{0}'", ex));
                throw ex;
            }
        }

        static void RoleEnvironment_Stopping(object sender, RoleEnvironmentStoppingEventArgs e)
        {
            try
            {
                Trace.TraceWarning("Roleinstance stopping.");



            }
            catch (Exception ex)
            {
                Trace.TraceError("Error in RoleEnvironment_Stopping " + ex.Message);
            }
            finally
            {

                UploadLogFile();
            }
            Environment.Exit(-1);
        }


        #endregion
    }
}
